// 版权所有2016 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package pprof

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"time"
	"unsafe"
)

// lostProfileEvent是将丢失的分析
// 事件归因于的函数。
// （名称显示在pprof图中。）
func lostProfileEvent() { lostProfileEvent() }

// funcPC返回函数值f的PC。
func funcPC(f interface{}) uintptr {
	return *(*[2]*uintptr)(unsafe.Pointer(&f))[1]
}

// profileBuilder从运行时交付的配置文件样本流以增量方式写入配置文件。
type profileBuilder struct {
	start      time.Time
	end        time.Time
	havePeriod bool
	period     int64
	m          profMap

	// 编码状态
	w         io.Writer
	zw        *gzip.Writer
	pb        protobuf
	strings   []string
	stringMap map[string]int
	locs      map[uintptr]locInfo // 从给定PC开始的locInfo列表。
	funcs     map[string]int      // 包路径限定函数名到函数。ID 
	mem       []memMap
	deck      pcDeck
}

type memMap struct {
	// 初始化为读取映射
	start         uintptr
	end           uintptr
	offset        uint64
	file, buildID string

	funcs symbolizeFlag
	fake  bool // 映射条目被伪造/proc/self/maps不可用
}

// symbolicFlag跟踪符号化结果。
// 0:未执行符号查找
// 1<<0（lookupTried）：已执行符号查找
// 1<<1（lookupFailed）：已执行符号查找，但失败
type symbolizeFlag uint8

const (
	lookupTried  symbolizeFlag = 1 << iota
	lookupFailed symbolizeFlag = 1 << iota
)

const (
	// 消息配置文件
	tagProfile_SampleType        = 1  // 重复值类型
	tagProfile_Sample            = 2  // 重复样本
	tagProfile_Mapping           = 3  // 重复映射
	tagProfile_Location          = 4  // 重复位置
	tagProfile_Function          = 5  // 重复函数
	tagProfile_StringTable       = 6  // 重复字符串
	tagProfile_DropFrames        = 7  // int64（字符串表索引）
	tagProfile_KeepFrames        = 8  // int64（字符串表索引）
	tagProfile_TimeNanos         = 9  // int64 
	tagProfile_DurationNanos     = 10 // int64 
	tagProfile_PeriodType        = 11 // ValueType（真可选字符串？）
	tagProfile_Period            = 12 // int64 
	tagProfile_Comment           = 13 // int64 
	tagProfile_DefaultSampleType = 14 // int64 

	// message ValueType 
	tagValueType_Type = 1 // int64（字符串表索引）
	tagValueType_Unit = 2 // int64（字符串表索引）

	// 消息示例
	tagSample_Location = 1 // 重复uint64 
	tagSample_Value    = 2 // 重复int64 
	tagSample_Label    = 3 // 重复标签

	// 消息标签
	tagLabel_Key = 1 // int64（字符串表索引）
	tagLabel_Str = 2 // int64（字符串表索引）
	tagLabel_Num = 3 // int64 

	// 消息映射
	tagMapping_ID              = 1  // uint64 
	tagMapping_Start           = 2  // uint64 
	tagMapping_Limit           = 3  // uint64 
	tagMapping_Offset          = 4  // uint64 
	tagMapping_Filename        = 5  // int64（字符串表索引）
	tagMapping_BuildID         = 6  // int64（字符串表索引）
	tagMapping_HasFunctions    = 7  // bool 
	tagMapping_HasFilenames    = 8  // bool 
	tagMapping_HasLineNumbers  = 9  // bool 
	tagMapping_HasInlineFrames = 10 // bool 

	// message Location 
	tagLocation_ID        = 1 // uint64 
	tagLocation_MappingID = 2 // uint64 
	tagLocation_Address   = 3 // uint64 
	tagLocation_Line      = 4 // 重复行

	// message行
	tagLine_FunctionID = 1 // uint64 
	tagLine_Line       = 2 // int64 

	// 消息函数
	tagFunction_ID         = 1 // uint64 
	tagFunction_Name       = 2 // int64（字符串表索引）
	tagFunction_SystemName = 3 // int64（字符串表索引）
	tagFunction_Filename   = 4 // int64（字符串表索引）
	tagFunction_StartLine  = 5 // int64 
)

// stringIndex将s添加到字符串表（如果尚未存在）
// 并返回字符串表中s的索引。
func (b *profileBuilder) stringIndex(s string) int64 {
	id, ok := b.stringMap[s]
	if !ok {
		id = len(b.strings)
		b.strings = append(b.strings, s)
		b.stringMap[s] = id
	}
	return int64(id)
}

func (b *profileBuilder) flush() {
	const dataFlush = 4096
	if b.pb.nest == 0 && len(b.pb.data) > dataFlush {
		b.zw.Write(b.pb.data)
		b.pb.data = b.pb.data[:0]
	}
}

// pbValueType将ValueType消息编码到b.pb。
func (b *profileBuilder) pbValueType(tag int, typ, unit string) {
	start := b.pb.startMessage()
	b.pb.int64(tagValueType_Type, b.stringIndex(typ))
	b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
	b.pb.endMessage(tag, start)
}

// pbSample将示例消息编码到b.pb。
func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
	start := b.pb.startMessage()
	b.pb.int64s(tagSample_Value, values)
	b.pb.uint64s(tagSample_Location, locs)
	if labels != nil {
		labels()
	}
	b.pb.endMessage(tagProfile_Sample, start)
	b.flush()
}

// pbLabel编码aLabel message to b.pb.
func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) {
	start := b.pb.startMessage()
	b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
	b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
	b.pb.int64Opt(tagLabel_Num, num)
	b.pb.endMessage(tag, start)
}

// pbLine将行消息编码为b.pb.
func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
	start := b.pb.startMessage()
	b.pb.uint64Opt(tagLine_FunctionID, funcID)
	b.pb.int64Opt(tagLine_Line, line)
	b.pb.endMessage(tag, start)
}

// pbMapping将映射消息编码为b.pb.
func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) {
	start := b.pb.startMessage()
	b.pb.uint64Opt(tagMapping_ID, id)
	b.pb.uint64Opt(tagMapping_Start, base)
	b.pb.uint64Opt(tagMapping_Limit, limit)
	b.pb.uint64Opt(tagMapping_Offset, offset)
	b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
	b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
	// TODO:如果样本中的所有符号都被符号化，则设置HasFunctions（hasFuncs）.
	// 决定如何处理HasInlineFrames和HaslineNumber.
	// 另外，处理带有
	// 不完整符号化结果的映射项的另一种方法是复制映射
	// 项（但具有不同的Has*字段值）使用符号化位置和非符号化位置的不同条目。使用调用帧扩展这一地址，这样我们可以缓存每个扩展。一般来说，调用帧占用整个堆栈，但在这种情况下，我们知道在调用帧中不会有跳过
	if hasFuncs {
		b.pb.bool(tagMapping_HasFunctions, true)
	}
	b.pb.endMessage(tag, start)
}

func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
	frames := runtime.CallersFrames([]uintptr{addr})
	frame, more := frames.Next()
	if frame.Function == "runtime.goexit" {
		// 如果我们看到runtime.goexit，则短路，因此下面的循环
		// 不会分配无用的空位置。
		return nil, 0
	}

	symbolizeResult := lookupTried
	if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
		symbolizeResult |= lookupFailed
	}

	if frame.PC == 0 {
		// 如果我们无法解析帧，至少可以组成
		// 一个合理的PC调用。这主要发生在测试中。aBCDEFG
		frame.PC = addr - 1
	}
	ret := []runtime.Frame{frame}
	for frame.Function != "runtime.goexit" && more == true {
		frame, more = frames.Next()
		ret = append(ret, frame)
	}
	return ret, symbolizeResult
}

type locInfo struct {
	id uint64

	// PC序列，包括回溯
	// 返回的伪PC，用于表示内联函数
	// https:
	pcs []uintptr
}

// newProfileBuilder返回一个新的profileBuilder。
// 获取的CPU评测数据从运行时可以添加
// 通过调用b.addCPUData，然后通过调用b.finish获得最终的配置文件
// 即可。
func newProfileBuilder(w io.Writer) *profileBuilder {
	zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
	b := &profileBuilder{
		w:         w,
		zw:        zw,
		start:     time.Now(),
		strings:   []string{""},
		stringMap: map[string]int{"": 0},
		locs:      map[uintptr]locInfo{},
		funcs:     map[string]int{},
	}
	b.readMapping()
	return b
}

// addCPUData将CPU配置文件数据添加到配置文件中。
// 数据必须是运行时交付的完整记录数。
func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {
	if !b.havePeriod {
		// 第一条记录是周期
		if len(data) < 3 {
			return fmt.Errorf("truncated profile")
		}
		if data[0] != 3 || data[2] == 0 {
			return fmt.Errorf("malformed profile")
		}
		// 数据[2]是以Hz为单位的采样率。转换为采样
		// 以纳秒为单位的周期。
		b.period = 1e9 / int64(data[2])
		b.havePeriod = true
		data = data[3:]
	}

	// 解析配置文件中的CPU样本。
	// 每个样本为3+n个单元64s:
	// 数据[0]=3+n 
	// 数据[1]=时间戳（忽略）
	// 数据[2]=计数
	// 数据[3:3+n]=堆栈
	// 如果计数为0且堆栈长度为1，
	// 这是运行时插入的溢出记录，指示堆栈[0]样本丢失。
	// 否则计数通常为1，
	// 但在一些特殊情况下，如丢失的非Go样本
	// 计数可能更大。
	// 因为有许多具有相同堆栈的样本到达，
	// 我们希望立即进行重复数据消除，我们使用b.m profMap进行重复数据消除。
	for len(data) > 0 {
		if len(data) < 3 || data[0] > uint64(len(data)) {
			return fmt.Errorf("truncated profile")
		}
		if data[0] < 3 || tags != nil && len(tags) < 1 {
			return fmt.Errorf("malformed profile")
		}
		count := data[2]
		stk := data[3:data[0]]
		data = data[data[0]:]
		var tag unsafe.Pointer
		if tags != nil {
			tag = tags[0]
			tags = tags[1:]
		}

		if count == 0 && len(stk) == 1 {
			// 溢出记录
			count = uint64(stk[0])
			stk = []uint64{
				// gentraceback保证
				// 堆栈中的PC可以无条件递减，并且
				// 仍然有效，因此我们必须这样做。
				uint64(funcPC(lostProfileEvent) + 1),
			}
		}
		b.m.lookup(stk, tag).count += int64(count)
	}
	return nil
}

// 构建完成并返回构建的配置文件。
func (b *profileBuilder) build() {
	b.end = time.Now()

	b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
	if b.havePeriod { // 必须是CPU配置文件
		b.pbValueType(tagProfile_SampleType, "samples", "count")
		b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")
		b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())
		b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")
		b.pb.int64Opt(tagProfile_Period, b.period)
	}

	values := []int64{0, 0}
	var locs []uint64

	for e := b.m.all; e != nil; e = e.nextAll {
		values[0] = e.count
		values[1] = e.count * b.period

		var labels func()
		if e.tag != nil {
			labels = func() {
				for k, v := range *(*labelMap)(e.tag) {
					b.pbLabel(tagSample_Label, k, v, 0)
				}
			}
		}

		locs = b.appendLocsForStack(locs[:0], e.stk)

		b.pbSample(values, locs, labels)
	}

	for i, m := range b.mem {
		hasFunctions := m.funcs == lookupTried // 已查找但未查找失败
		b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)
	}

	// TODO:标记配置文件\u DropFrames有什么要求吗？
	// TODO:有什么关于tagProfile_KeepFrames的吗？

	b.pb.strings(tagProfile_StringTable, b.strings)
	b.zw.Write(b.pb.data)
	b.zw.Close()
}

// appendLocsForStack将给定堆栈跟踪的位置ID追加到给定的
// 位置ID切片，locs。堆栈中的地址是返回PC或1+
// 运行时回溯函数返回的内联标记的PC。
// 
// 它可能会发送到b.pb，因此必须没有正在进行的消息编码。
func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {
	b.deck.reset()

	// 最后一帧可能被截断。恢复丢失的内联帧。
	stk = runtime_expandFinalInlineFrame(stk)

	for len(stk) > 0 {
		addr := stk[0]
		if l, ok := b.locs[addr]; ok {
			// 如果有任何待处理的累积信息，请首先记录位置。
			if id := b.emitLocation(); id > 0 {
				locs = append(locs, id)
			}

			// 然后记录缓存位置。
			locs = append(locs, l.id)

			// 跳过匹配的PC。
			// 
			// 即使stk由于堆栈深度
			// 限制而被截断，上面的ExpandFinalLineFrame已经修复了截断，确保它足够长。
			stk = stk[len(l.pcs):]
			continue
		}

		frames, symbolizeResult := allFrames(addr)
		if len(frames) == 0 { // runtime.goexit。
			if id := b.emitLocation(); id > 0 {
				locs = append(locs, id)
			}
			stk = stk[1:]
			continue
		}

		if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {
			stk = stk[1:]
			continue
		}
		// 添加失败，因为此添加未与组中的现有PC机
		// 内联。刷新数据组并重试处理
		// 此电脑。
		if id := b.emitLocation(); id > 0 {
			locs = append(locs, id)
		}

		// 再次检查缓存-上一个位置添加了一个新条目
		if l, ok := b.locs[addr]; ok {
			locs = append(locs, l.id)
			stk = stk[len(l.pcs):] // 跳过匹配的电脑。
		} else {
			b.deck.tryAdd(addr, frames, symbolizeResult) // 必须成功。
			stk = stk[1:]
		}
	}
	if id := b.emitLocation(); id > 0 { // 发射剩余位置。
		locs = append(locs, id)
	}
	return locs
}

// pcDeck是从运行时返回的堆栈跟踪中检测内联函数序列的助手。
// 
// 运行时的trackback函数返回的堆栈跟踪是完全扩展的（至少对于Go函数），包括表示
// 内联函数的伪PC。profile协议要求内联函数在一个位置消息中编码为
// 。
// https:
// 
// 运行时不直接公开帧是否用于内联函数
// 查找调试信息不理想，因此，我们使用一种启发式方法来过滤
// 伪PC，并恢复内联和入口函数。内联函数
// 具有以下属性：
// Frame的Func为nil（注意：对于非Go函数也为true），而
// Frame的条目匹配其条目函数Frame的条目（注意：对于递归调用和非Go函数也可能为true），并且
// Frame的名称与其输入函数Frame的名称不匹配（注意：内联函数不能直接递归）。
// 
// 
// 我们使用pcDeck暂时保留观察到的PC及其扩展帧
// 直到我们观察到入口函数帧为止，因为我们逐个读取和处理堆栈跟踪中的PC。
type pcDeck struct {
	pcs             []uintptr
	frames          []runtime.Frame
	symbolizeResult symbolizeFlag
}

func (d *pcDeck) reset() {
	d.pcs = d.pcs[:0]
	d.frames = d.frames[:0]
	d.symbolizeResult = 0
}

// tryAdd尝试添加pc和从pc扩展的帧（最有可能是一个，因为堆栈跟踪已完全扩展）和符号化结果
// 到甲板上去。如果失败，调用方需要刷新数据组并重试。
func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) {
	if existing := len(d.pcs); existing > 0 {
		// “d.frames”都是从一台“pc”扩展而来，代表所有
		// 内联函数，因此我们只检查最后一个。
		newFrame := frames[0]
		last := d.frames[existing-1]
		if last.Func != nil { // 最后一帧无法内联。脸红
			return false
		}
		if last.Entry == 0 || newFrame.Entry == 0 { // 可能不是Go函数。不要试图合并。
			return false
		}

		if last.Entry != newFrame.Entry { // newFrame用于另一个函数。
			return false
		}
		if last.Function == newFrame.Function { // 可能是递归。
			return false
		}
	}
	d.pcs = append(d.pcs, pc)
	d.frames = append(d.frames, frames...)
	d.symbolizeResult |= symbolizeResult
	return true
}

// emitLocation发出记录在数据组
// 中的新位置和功能信息，并返回配置文件protobuf中编码的位置ID。
// 它向b.pb发送消息，因此必须没有正在进行的消息编码。
// 它重置了甲板。ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊＊
func (b *profileBuilder) emitLocation() uint64 {
	if len(b.deck.pcs) == 0 {
		return 0
	}
	defer b.deck.reset()

	addr := b.deck.pcs[0]
	firstFrame := b.deck.frames[0]

	type newFunc struct {
		id         uint64
		name, file string
	}
	newFuncs := make([]newFunc, 0, 8)

	id := uint64(len(b.locs)) + 1
	b.locs[addr] = locInfo{id: id, pcs: append([]uintptr{}, b.deck.pcs...)}

	start := b.pb.startMessage()
	b.pb.uint64Opt(tagLocation_ID, id)
	b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
	for _, frame := range b.deck.frames {
		// 在帧扩展中写出每一行。
		funcID := uint64(b.funcs[frame.Function])
		if funcID == 0 {
			funcID = uint64(len(b.funcs)) + 1
			b.funcs[frame.Function] = int(funcID)
			newFuncs = append(newFuncs, newFunc{funcID, frame.Function, frame.File})
		}
		b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
	}
	for i := range b.mem {
		if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {
			b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))

			m := b.mem[i]
			m.funcs |= b.deck.symbolizeResult
			b.mem[i] = m
			break
		}
	}
	b.pb.endMessage(tagProfile_Location, start)

	// 写出我们在帧扩展过程中发现的函数。
	for _, fn := range newFuncs {
		start := b.pb.startMessage()
		b.pb.uint64Opt(tagFunction_ID, fn.id)
		b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
		b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
		b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
		b.pb.endMessage(tagProfile_Function, start)
	}

	b.flush()
	return id
}

// readMapping读取/proc/self/映射并写入到b.pb的映射。
// 它将映射的地址范围保存在b.mem中，以便在发送位置时使用。
func (b *profileBuilder) readMapping() {
	data, _ := os.ReadFile("/proc/self/maps")
	parseProcSelfMaps(data, b.addMapping)
	if len(b.mem) == 0 { // pprof需要一个映射条目，所以是假的。
		b.addMappingEntry(0, 0, 0, "", "", true)
		// TODO（hyangah）：使addMapping返回*memMap或
		// 使用memMap结构，并去掉addMappingEntry 
		// 它需要一组位置参数。
	}
}

func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
	// /$cat/proc/self/maps 
	// 00400000-0040b000 r-xp00000000 fc:01787766/bin/cat 
	// 0060a000-0060b000 r--p0000a000 fc:01787766/bin/cat 
	// 0060b000-0060c000 rw-p000b000 fc:01787766/bin/cat 
	// 014ab000-014cc000 rw-p 00000000:00[堆]
	// 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064/usr/lib/locale/locale archive 
	// 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226/lib/x86_64-linux-gnu/libc-2.19.所以
	// 7D77B36000-7f7d77d36000-7D77D36000--p 001ba000 fc:01 1180226/lib/x86_64-linux-gnu/libc-2.19.so 
	// 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226/lib/x86_64-linux-gnu/libc-2.19.so 
	// 7F7D77D4100-7F7D77D6400R-xp 00000000 fc:01 1180217/lib/x86_64-linux-gnu/ld-2.19。所以
	// 7f7d77f3f000-7F7D77F4200 rw-p 00000000:00 
	// 7f7d77f61000-7D77F61000-7f7d77f63000-rw-p 00000000:00
	// 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217/lib/x86_64-linux-gnu/ld-2.19.so 
	// 7f7d77f65000-7f7d77f66000 rw-p 00000000:00 
	// ffc342000-7ffc3000-7ffc300:00[stack]
	// 7ffc34343000-7ffc34345000 r-xp 00000000:00[vdso]
	// FFFFFFFF 600000-FFFFFFFF 601000 r-xp 00000000:00 0[vsyscall]

	var line []byte
	// 下一步删除并返回行中的下一个字段。
	// 它还会从行中删除字段后面的所有空格。
	next := func() []byte {
		j := bytes.IndexByte(line, ' ')
		if j < 0 {
			f := line
			line = nil
			return f
		}
		f := line[:j]
		line = line[j+1:]
		for len(line) > 0 && line[0] == ' ' {
			line = line[1:]
		}
		return f
	}

	for len(data) > 0 {
		i := bytes.IndexByte(data, '\n')
		if i < 0 {
			line, data = data, nil
		} else {
			line, data = data[:i], data[i+1:]
		}
		addr := next()
		i = bytes.IndexByte(addr, '-')
		if i < 0 {
			continue
		}
		lo, err := strconv.ParseUint(string(addr[:i]), 16, 64)
		if err != nil {
			continue
		}
		hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64)
		if err != nil {
			continue
		}
		perm := next()
		if len(perm) < 4 || perm[2] != 'x' {
			// 只对可执行映射感兴趣。
			continue
		}
		offset, err := strconv.ParseUint(string(next()), 16, 64)
		if err != nil {
			continue
		}
		next()          // dev 
		inode := next() // 索引节点
		if line == nil {
			continue
		}
		file := string(line)

		// 修剪删除的文件标记。
		deletedStr := " (deleted)"
		deletedLen := len(deletedStr)
		if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {
			file = file[:len(file)-deletedLen]
		}

		if len(inode) == 1 && inode[0] == '0' && file == "" {
			// 大页面文本映射将
			// 已映射但未填充的内存的初始片段列为索引节点0。
			// 不要报告那个部分。
			// 但是[vdso]和[vsyscall]是索引节点0，所以让非空文件名通过。
			continue
		}

		// TODO:pprof的重新映射进行了两次调整：
		// 1。如果首先有一个/anon_hugepage映射，并且它是
		// 连续到下一个映射，请删除/anon_hugepage。
		// 2。如果起始偏移量=0x400000，则将起始值更改为0x400000，并将偏移量更改为0。
		// 没有迹象表明为什么需要这两种方法。
		// 让我们试着不做这些，看看会有什么中断。
		// 如果我们确实需要它们，它们会先到这里，然后我们再将映射输入到b.mem中。

		buildID, _ := elfBuildID(file)
		addMapping(lo, hi, offset, file, buildID)
	}
}

func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
	b.addMappingEntry(lo, hi, offset, file, buildID, false)
}

func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {
	b.mem = append(b.mem, memMap{
		start:   uintptr(lo),
		end:     uintptr(hi),
		offset:  offset,
		file:    file,
		buildID: buildID,
		fake:    fake,
	})
}
